import numpy as np
import pickle
from sklearn.cluster import KMeans
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder
from confg import constants
from confg.products import products


class SnackRecommender:
    def __init__(self, products):
        self.products = products
        self.product_ids = [p['id'] for p in products]
        self._prepare_data()
        self._train_models()

    def _prepare_data(self):
        """准备商品特征数据"""
        # 数值特征
        numerical_features = []
        # 分类特征
        categorical_features = []

        for p in self.products:
            # 数值特征: [价格, 卡路里, 蛋白质, 脂肪, 碳水, 评分]
            numerical = [
                p['price'],
                p['calories'],
                p['protein'],
                p['fat'],
                p['carbs'],
                p['rating']
            ]
            numerical_features.append(numerical)

            # 分类特征: 口味+类别+标签的组合
            cat_features = p['flavors'] + [p['category']] + p['tags']
            categorical_features.append(cat_features)

        # 数值特征标准化
        self.num_scaler = MinMaxScaler()
        self.scaled_numerical = self.num_scaler.fit_transform(numerical_features)

        # 分类特征编码
        all_cat_features = constants.FLAVORS + constants.CATEGORIES + list(
            set([tag for p in self.products for tag in p['tags']]))
        self.cat_encoder = OneHotEncoder(categories=[all_cat_features], handle_unknown='ignore')
        encoded_cat = []
        for cat_list in categorical_features:
            # 创建多热编码
            encoded = np.zeros(len(all_cat_features))
            indices = [all_cat_features.index(f) for f in cat_list if f in all_cat_features]
            encoded[indices] = 1
            encoded_cat.append(encoded)

        self.encoded_cat = np.array(encoded_cat)

        # 组合特征
        # 将缩放后的数值特征和编码后的类别特征水平堆叠起来
        self.product_features = np.hstack([self.scaled_numerical, self.encoded_cat])
        # 计算特征维度
        self.num_feature_dim = len(numerical_features[0])  # 6
        self.cat_feature_dim = len(all_cat_features)  # 28
        self.total_feature_dim = self.num_feature_dim + self.cat_feature_dim  # 34

    def _train_models(self):
        """训练推荐模型"""
        # 商品相似度模型 (基于内容)
        self.similarity_model = NearestNeighbors(n_neighbors=5, metric='cosine')
        self.similarity_model.fit(self.product_features)

        # 用户聚类模型 - 使用与商品特征相同的维度
        # 生成模拟用户特征
        self.user_features = np.random.rand(500, self.total_feature_dim)  # 使用正确的特征维度
        self.user_scaler = MinMaxScaler()
        scaled_user_features = self.user_scaler.fit_transform(self.user_features)
        self.user_cluster_model = KMeans(n_clusters=5, random_state=42)
        self.user_cluster_model.fit(scaled_user_features)

    def save_models(self, filepath):
        """保存模型到文件"""
        with open(filepath, 'wb') as f:
            pickle.dump({
                'num_scaler': self.num_scaler,
                'cat_encoder': self.cat_encoder,
                'similarity_model': self.similarity_model,
                'user_cluster_model': self.user_cluster_model,
                'user_scaler': self.user_scaler
            }, f)

    def recommend(self, user_preferences, user_id=None):
        """
        为用户生成推荐
        user_preferences: 用户偏好字典
            {
                "flavor_profile": ["口味偏好"],
                "category_pref": ["零食类别"],
                "health_constraints": ["健康限制"],
                "price_range": "价格区间"
            }
        """
        # 创建用户偏好特征
        user_vector = self._create_user_vector(user_preferences)

        # 获取用户聚类
        user_cluster = self.user_cluster_model.predict(user_vector.reshape(1, -1))[0]

        # 查找相似商品
        _, indices = self.similarity_model.kneighbors(user_vector.reshape(1, -1), n_neighbors=5)

        recommendations = []
        for idx in indices[0]:
            product = self.products[idx]

            # 检查是否符合健康约束
            if not self._meets_health_constraints(product, user_preferences['health_constraints']):
                continue

            # 检查价格范围
            if not self._meets_price_range(product, user_preferences['price_range']):
                continue

            # 计算匹配分数
            match_score = self._calculate_match_score(product, user_preferences)

            # 创建推荐条目
            recommendations.append({
                "product_id": product['id'],
                "name": product['name'],
                "category": product['category'],
                "price": product['price'],
                "match_score": match_score,
                "match_reason": self._generate_match_reason(product, user_preferences)
            })

            if len(recommendations) >= 3:  # 最多推荐3个
                break

        # 按匹配分数排序
        recommendations.sort(key=lambda x: x['match_score'], reverse=True)

        return recommendations, user_cluster

    def _create_user_vector(self, preferences):
        """创建用户偏好特征向量"""
        # 数值部分: [平均价格偏好, 平均卡路里偏好, 健康关注度]
        price_mapping = {"经济型(<15)": 10, "标准型(15-30)": 22.5, "高端型(>30)": 40}
        avg_price = price_mapping.get(preferences['price_range'], 22.5)

        # 健康约束强度
        health_strength = 0.2  # 默认
        if preferences['health_constraints'] and preferences['health_constraints'][0] != "无":
            health_strength = 0.8 if "低" in "".join(preferences['health_constraints']) else 0.5

        # 数值特征
        num_vector = [avg_price, 300, health_strength, 0.5, 0.5, 4.5]  # 最后三个是占位符

        # 分类特征
        all_features = constants.FLAVORS + constants.CATEGORIES
        cat_vector = np.zeros(len(all_features))

        # 设置口味偏好
        for flavor in preferences['flavor_profile']:
            if flavor in constants.FLAVORS:
                idx = constants.FLAVORS.index(flavor)
                cat_vector[idx] = 1

        # 设置类别偏好
        for category in preferences['category_pref']:
            if category in constants.CATEGORIES:
                idx = constants.CATEGORIES.index(category) + len(constants.FLAVORS)
                cat_vector[idx] = 1

        # 组合特征向量

        scaled_num = self.num_scaler.transform([num_vector])[0]
        user_vector = np.concatenate([scaled_num, cat_vector])

        # 检查特征向量维度
        if len(user_vector) != self.total_feature_dim:
            # 如果维度不匹配，填充或截断
            if len(user_vector) < self.total_feature_dim:
                padding = np.zeros(self.total_feature_dim - len(user_vector))
                user_vector = np.concatenate([user_vector, padding])
            else:
                user_vector = user_vector[:self.total_feature_dim]

        return user_vector

    def _meets_health_constraints(self, product, constraints):
        """检查产品是否符合健康约束"""
        if "无" in constraints or not constraints:
            return True

        for constraint in constraints:
            if constraint == "低糖" and product['carbs'] > 40:
                return False
            if constraint == "低脂" and product['fat'] > 15:
                return False
            if constraint == "低卡" and product['calories'] > 350:
                return False
            if constraint == "高蛋白" and product['protein'] < 10:
                return False
            if constraint == "无麸质" and "饼干" in product['category']:  # 简化逻辑
                return False

        return True

    def _meets_price_range(self, product, price_range):
        """检查产品是否符合价格范围"""
        if price_range == "经济型(<15)":
            return product['price'] < 15
        elif price_range == "标准型(15-30)":
            return 15 <= product['price'] <= 30
        elif price_range == "高端型(>30)":
            return product['price'] > 30
        return True

    def _calculate_match_score(self, product, preferences):
        """计算产品匹配分数"""
        score = 0.0

        # 口味匹配
        flavor_match = len(set(product['flavors']) & set(preferences['flavor_profile']))
        score += flavor_match * 0.2

        # 类别匹配
        if product['category'] in preferences['category_pref']:
            score += 0.3

        # 价格匹配 - 使用高斯衰减
        price_diff = 0
        if preferences['price_range'] == "经济型(<15)":
            price_diff = max(0, product['price'] - 15)
        elif preferences['price_range'] == "标准型(15-30)":
            mid = 22.5
            price_diff = min(abs(product['price'] - 15), abs(product['price'] - 30))
        else:  # 高端型
            price_diff = max(0, 30 - product['price'])

        price_score = np.exp(-0.1 * price_diff)
        score += price_score * 0.3

        # 健康约束匹配
        health_score = 1.0 if self._meets_health_constraints(product, preferences['health_constraints']) else 0.0
        score += health_score * 0.2

        return min(1.0, max(0.5, score))  # 限制在0.5-1.0之间

    def _generate_match_reason(self, product, preferences):
        """生成匹配原因描述"""
        reasons = []

        # 口味匹配
        matched_flavors = set(product['flavors']) & set(preferences['flavor_profile'])
        if matched_flavors:
            reasons.append(f"匹配口味: {', '.join(matched_flavors)}")

        # 类别匹配
        if product['category'] in preferences['category_pref']:
            reasons.append(f"匹配类别: {product['category']}")

        # 健康约束
        if preferences['health_constraints'] and preferences['health_constraints'][0] != "无":
            reasons.append(f"符合健康限制: {', '.join(preferences['health_constraints'])}")

        # 价格范围
        reasons.append(f"价格范围: {preferences['price_range']}")

        return "; ".join(reasons)